using System;
using System.Collections.ObjectModel;

namespace Narwhunderful.Aphelia.Framework.IO.Serialization
{

    public sealed class TypeInfo
    {
        private static readonly TypeInfoCollection Cache = new TypeInfoCollection();

        public bool IsSimple { get; set; }
        public bool IsArray { get; set; }
        public bool IsEnumerable { get; set; }
        public bool IsCollection { get; set; }
        public bool IsDictionary { get; set; }
        public Type ElementType { get; set; }
        public Type KeyType { get; set; }
        public int ArrayDimensionCount { get; set; }
        public Type Type { get; set; }

        public static TypeInfo GetTypeInfo(object obj)
        {
            if (obj == null) throw new ArgumentNullException("obj");

            Type type = obj.GetType();
            return GetTypeInfo(type);
        }

        public static TypeInfo GetTypeInfo(Type type)
        {
            // check if Info is in cache
            TypeInfo typeInfo = Cache.TryGetTypeInfo(type);
            if (typeInfo == null)
            {
                // no info in cache yet
                typeInfo = new TypeInfo();
                typeInfo.Type = type;

                typeInfo.IsSimple = Helpers.IsSimple(type);

                // Only non simple types can be Collections
                if (!typeInfo.IsSimple)
                {
                    // check if it is an Array
                    typeInfo.IsArray = Helpers.IsArray(type);

                    if (typeInfo.IsArray)
                    {
                        // Array? What is its element type?
                        if (type.HasElementType)
                        {
                            typeInfo.ElementType = type.GetElementType();
                        }

                        // How many dimensions
                        typeInfo.ArrayDimensionCount = type.GetArrayRank();
                    }
                    else
                    {
                        // It is not Array, maybe Enumerable?
                        typeInfo.IsEnumerable = Helpers.IsEnumerable(type);
                        if (typeInfo.IsEnumerable)
                        {
                            // it is Enumerable maybe Collection?
                            typeInfo.IsCollection = Helpers.IsCollection(type);

                            if (typeInfo.IsCollection)
                            {
                                // Sure it is a Collection, but maybe Dictionary also?
                                typeInfo.IsDictionary = Helpers.IsDictionary(type);

                                // Fill its key and value types, if the listing is generic
                                bool elementTypeDefinitionFound;
                                var examinedType = type;
                                do
                                {
                                    elementTypeDefinitionFound = FillKeyAndElementType(typeInfo, examinedType);
                                    examinedType = examinedType.BaseType;
                                    // until key and element definition was found, or the base typ is an object
                                } while (!elementTypeDefinitionFound && examinedType != null && examinedType != typeof(object));
                            }
                        }
                    }
                }

                Cache.Add(typeInfo);
            }

            return typeInfo;
        }

        private static bool FillKeyAndElementType(TypeInfo typeInfo, Type type)
        {
            if (type.IsGenericType)
            {
                Type[] arguments = type.GetGenericArguments();

                switch (arguments.Length)
                {
                    case 2:
                        typeInfo.KeyType = arguments[0];
                        typeInfo.ElementType = arguments[1];
                        break;
                    case 1:
                        typeInfo.ElementType = arguments[0];
                        break;
                    default:
                        break;
                }
            
                return arguments.Length > 0;
            }
            return false;
        }
    }

    public sealed class TypeInfoCollection : KeyedCollection<Type, TypeInfo>
    {

        public TypeInfo TryGetTypeInfo(Type type)
        {
            foreach (TypeInfo item in Items)
            {
                if (item.Type == type)
                {
                    return item;
                }
            }
            return null;
        }

        protected override Type GetKeyForItem(TypeInfo item)
        {
            return item.Type;
        }
    }

}
